コード例 #1
0
ファイル: business_rules.py プロジェクト: uktrade/tamato
    def get_linked_models(
        cls: Type[BusinessRule],
        model: TrackedModel,
        transaction,
    ) -> Iterator[TrackedModel]:
        """
        Returns latest approved model instances that have relations to the
        passed ``model`` and have this business rule listed in their
        ``business_rules`` attribute.

        :param model TrackedModel: Get models linked to this model instance
        :param transaction Transaction: Get latest approved versions of linked
            models as of this transaction
        :rtype Iterator[TrackedModel]: The linked models
        """
        for field, related_model in get_relations(type(model)).items():
            business_rules = getattr(related_model, "business_rules", [])
            if cls in business_rules:
                if field.one_to_many or field.many_to_many:
                    related_instances = getattr(model, field.get_accessor_name()).all()
                else:
                    related_instances = [getattr(model, field.name)]
                for instance in related_instances:
                    try:
                        yield instance.version_at(transaction)
                    except TrackedModel.DoesNotExist:
                        # `related_instances` will contain all instances, even
                        # deleted ones, and `version_at` will return
                        # `DoesNotExist` if the item has been deleted as of a
                        # certain transaction. That's ok, because we can just
                        # skip running business rules against deleted things.
                        continue
コード例 #2
0
    def in_use(self, transaction=None, *relations: str) -> bool:
        """
        Returns True if there are any models that are using this one as of the
        specified transaction.

        This can be any model this model is related to, but ignoring any
        subrecords (because e.g. a footnote is not considered "in use by" its
        own description) and then filtering for only things that link _to_ this
        model.

        The list of relations can be filtered by passing in the name of a
        relation. If a name is passed in that does not refer to a relation on
        this model, ``ValueError`` will be raised.
        """
        # Get the list of models that use models of this type.
        class_ = self.__class__
        using_models = set(
            relation.name
            for relation in (get_relations(class_).keys() -
                             get_subrecord_relations(class_) -
                             get_models_linked_to(class_).keys()))

        # If the user has specified names, check that they are sane
        # and then filter the relations to them,
        if relations:
            bad_names = set(relations) - set(using_models)
            if any(bad_names):
                raise ValueError(
                    f"{bad_names} are unknown relations; use one of {using_models}",
                )

            using_models = {
                relation
                for relation in using_models if relation in relations
            }

        # If this model doesn't have any using relations, it cannot be in use.
        if not any(using_models):
            return False

        # If we find any objects for any relation, then the model is in use.
        for relation_name in using_models:
            relation_queryset = self.in_use_by(relation_name, transaction)
            if relation_queryset.exists():
                return True

        return False
コード例 #3
0
    def get_descriptions(self,
                         transaction=None,
                         request=None) -> TrackedModelQuerySet:
        """
        Get the latest descriptions related to this instance of the Tracked
        Model.

        If there is no Description relation existing a `NoDescriptionError` is raised.

        If a transaction is provided then all latest descriptions that are either approved
        or in the workbasket of the transaction up to the transaction will be provided.
        """
        try:
            descriptions_model = self.descriptions.model
        except AttributeError as e:
            raise NoDescriptionError(
                f"Model {self.__class__.__name__} has no descriptions relation.",
            ) from e

        for field, model in get_relations(descriptions_model).items():
            if isinstance(self, model):
                field_name = field.name
                break
        else:
            raise NoDescriptionError(
                f"No foreign key back to model {self.__class__.__name__} "
                f"found on description model {descriptions_model.__name__}.", )

        filter_kwargs = {
            f"{field_name}__{key}": value
            for key, value in self.get_identifying_fields().items()
        }

        query = descriptions_model.objects.filter(**filter_kwargs).order_by(
            *descriptions_model._meta.ordering)

        if transaction:
            return query.approved_up_to_transaction(transaction)

        # if a global transaction variable is available, filter objects approved up to this
        if get_current_transaction():
            return query.current()

        return query.latest_approved()
コード例 #4
0
    def in_use_by(self,
                  via_relation: str,
                  transaction=None) -> QuerySet[TrackedModel]:
        """
        Returns all of the models that are referencing this one via the
        specified relation and exist as of the passed transaction.

        ``via_relation`` should be the name of a relation, and a ``KeyError``
        will be raised if the relation name is not valid for this model.
        Relations are accessible via get_relations helper method.
        """
        relation = {r.name: r
                    for r in get_relations(self.__class__).keys()
                    }[via_relation]
        remote_model = relation.remote_field.model
        remote_field_name = get_accessor(relation.remote_field)

        return remote_model.objects.filter(
            **{
                f"{remote_field_name}__version_group": self.version_group
            }).approved_up_to_transaction(transaction)
コード例 #5
0
ファイル: utils.py プロジェクト: uktrade/tamato
def build_dependency_tree(
        use_subrecord_codes: bool = False) -> Dict[str, Set[str]]:
    """
    Build a dependency tree of all the TrackedModel subclasses mapped by record
    code.

    The return value is a dictionary, mapped by record code, where the mapped values
    are sets listing all the other record codes the mapped record code depends on.

    A dependency is defined as any foreign key relationship to another record code.
    An example output is given below.

    .. code:: python

        {
            "220": {"215", "210"},
        }
    """

    dependency_map = {}

    record_codes = {
        code
        for subclass in TrackedModel.__subclasses__()
        for code in get_record_codes(subclass)
    }

    for subclass in TrackedModel.__subclasses__():
        for record_code in get_record_codes(subclass):
            if record_code not in dependency_map:
                dependency_map[record_code] = set()

            for relation in get_relations(subclass).values():
                relation_codes = get_record_codes(relation)

                for relation_code in relation_codes:
                    if relation_code != record_code and relation_code in record_codes:
                        dependency_map[record_code].add(relation_code)

    return dependency_map
コード例 #6
0
 def described_object_field(cls) -> Field:
     for rel in get_relations(cls).keys():
         if rel.name.startswith("described_"):
             return rel
     raise TypeError(f"{cls} should have a described field.")