def approved_query_filter(self, prefix=""): return Q( **{ f"{prefix}transaction__workbasket__status__in": WorkflowStatus.approved_statuses(), f"{prefix}transaction__workbasket__approver__isnull": False, } )
def handle(self, *args: Any, **options: Any) -> Optional[str]: workbaskets = WorkBasket.objects.order_by("updated_at").all() workbasket_statuses = set() if options["status"]: workbasket_statuses.update(options["status"]) if options.get("approved_statuses"): workbasket_statuses.update(WorkflowStatus.approved_statuses()) if workbasket_statuses: workbaskets = workbaskets.filter(status__in=options["status"]) if options.get("workbasket_ids"): workbaskets = workbaskets.filter(id__in=options["workbasket_ids"]) output_format = (WorkBasketOutputFormat.COMPACT if options["compact"] else WorkBasketOutputFormat.READABLE) show_transaction_info = options["transactions"] self.output_workbaskets( workbaskets, show_transaction_info, output_format=output_format, )
def infer_draft_transaction_partitions(apps, schemaeditor): """Infer the partition of Transactions to DRAFT if they are in an unapproved workbasket that isn't the first (seed) Workbasket.""" from common.models.transactions import TransactionPartition from workbaskets.validators import WorkflowStatus non_seed_transactions(apps).exclude( workbasket__status__in=WorkflowStatus.approved_statuses(), ).update( partition=TransactionPartition.DRAFT.value)
def get_partition(self, status: WorkflowStatus) -> TransactionPartition: """ :param status: Workbasket status :return: TransactionPartition that maps to the workbasket status. """ if status not in WorkflowStatus.approved_statuses(): # Bail out early if not approved and avoid query in the next if block. return TransactionPartition.DRAFT return self.get_approved_partition()
def infer_revision_transaction_partitions(apps, schemaeditor): """Infer the partition of Transactions to REVISION if they are not in the first workbasket and their workbasket status is approved.""" from common.models.transactions import TransactionPartition from workbaskets.validators import WorkflowStatus apps.get_model("common", "Transaction") apps.get_model("workbaskets", "Workbasket") non_seed_transactions(apps).filter( workbasket__status__in=WorkflowStatus.approved_statuses(), ).update( partition=TransactionPartition.REVISION.value)
def get_approved_partition(self) -> TransactionPartition: """ :return REVISION: if any approved workbaskets exist otherwise SEED_FILE. Implements the policy where approved transactions in the first workbasket are from the seed file and the rest of the approved transactions are revisions. Usage note: This must to be called before workbasket status is changed otherwise it will only ever return REVISION. """ if WorkBasket.objects.filter( status__in=WorkflowStatus.approved_statuses(), ).exists(): return TransactionPartition.REVISION return TransactionPartition.SEED_FILE
def save(self, *args, force_write=False, **kwargs): if not force_write and not self._can_write(): raise IllegalSaveError( "TrackedModels cannot be updated once written and approved. " "If writing a new row, use `.new_draft` instead", ) if not hasattr(self, "version_group"): self.version_group = self._get_version_group() return_value = super().save(*args, **kwargs) if self.transaction.workbasket.status in WorkflowStatus.approved_statuses(): self.version_group.current_version = self self.version_group.save() return return_value
def add_arguments(self, parser: CommandParser) -> None: statuses = [str(w) for w in WorkflowStatus] parser.add_argument( "--status", nargs="+", help=("Only list WorkBaskets with the given STATUS. The default, " "without use of this flag, is to list all WorkBaskets." f"STATUS can be any of {', '.join(statuses)}"), ) approved_statuses = [ status.name for status in WorkflowStatus.approved_statuses() ] parser.add_argument( "-a", "--approved-statuses", dest="approved", action="store_true", help= f"List workbaskets with ANY of the approved statuses, equivalent to: [{', '.join(approved_statuses)}]", ) parser.add_argument( "-c", "--compact", action="store_true", help="Output one workbasket per line.", ) parser.add_argument( "-t", "--transactions", action="store_true", help="Output first / last transactions.", ) parser.add_argument( "workbasket_ids", help=("Comma-separated list of workbasket ids to filter to"), type=ast.literal_eval, )
def save(self, *args, force_write=False, **kwargs): """ Save the model to the database. :param force_write bool: Ignore append-only restrictions and write to the database even if the model already exists """ if not force_write and not self._can_write(): raise IllegalSaveError( "TrackedModels cannot be updated once written and approved. " "If writing a new row, use `.new_draft` instead", ) if not hasattr(self, "version_group"): self.version_group = self._get_version_group() return_value = super().save(*args, **kwargs) if self.transaction.workbasket.status in WorkflowStatus.approved_statuses( ): self.version_group.current_version = self self.version_group.save() auto_fields = { field for field in self.auto_value_fields if field.attname in self.__dict__ and isinstance(self.__dict__.get(field.attname), (Expression, F)) } # If the model contains any fields that are built in the database, the # fields will still contain the expression objects. So remove them now # and Django will lazy fetch the real values if they are accessed. for field in auto_fields: delattr(self, field.name) return return_value
def approved(self): return self.status in WorkflowStatus.approved_statuses()
def is_not_approved(self): return self.exclude( status__in=WorkflowStatus.approved_statuses(), approver__isnull=False, )
def is_approved(self): return self.filter( status__in=WorkflowStatus.approved_statuses(), approver__isnull=False, )
def _can_write(self): return not (self.pk and self.transaction.workbasket.status in WorkflowStatus.approved_statuses())
@pytest.mark.parametrize("status", [WorkflowStatus.EDITING, WorkflowStatus.PROPOSED]) def test_draft_status_as_transaction_partition_draft_no_first_seed(status, ): """When first_partition_is_seed is False, draft workbaskets should generate a DRAFT transaction partition value.""" partition_scheme = SEED_FIRST assert isinstance(partition_scheme, TransactionPartitionScheme) partition = partition_scheme.get_partition(status) assert partition == TransactionPartition.DRAFT @pytest.mark.parametrize("status", WorkflowStatus.approved_statuses()) @pytest.mark.parametrize( "partition_scheme,expected_partition", [ ( UserTransactionPartitionScheme(TransactionPartition.SEED_FILE, "test text"), TransactionPartition.SEED_FILE, ), ( UserTransactionPartitionScheme(TransactionPartition.REVISION, "test text"), TransactionPartition.REVISION, ), ], )