signoffs.core.approvals#

An Approval manages logic for collecting and sequencing one or more Signoffs.

Approval Types are registered subclasses of AbstractApproval

  • they define the behaviour, sequencing logic, and state transition logic for an Approval instance.

Persistence layer for Approval state is provided by a Stamp model (think ā€œStamp of Approvalā€ or TimeStamp)

  • one concrete Stamp model can back any number of Approval Types

  • an Approval provides the business and presentation logic for a Stamp instance.

Module Contents#

Classes#

AbstractApproval

Defines the semantics for Approving something using a sequence of one or more Signoffs

BaseApproval

A base Approval Type to be used as base class or to register concrete Approval Types Concrete Types will require a concrete Stamp Model to back the approval.

DefaultApprovalBusinessLogic

Defines the business logic for Approving and Revoking an Approval instance

ApprovalLogic

Public API: Alias for `DefaultApprovalBusinessLogic

Functions#

revoke_approval

Force revoke the given approval for user regardless of permissions or approval state!

API#

signoffs.core.approvals.revoke_approval(approval, user, reason='')[source]#

Force revoke the given approval for user regardless of permissions or approval state!

Default implementation revokes ALL related signets on behalf of the user

  • a user with permission to revoke an approval DOES NOT NEED permission to revoke all signoffs within!

class signoffs.core.approvals.AbstractApproval(stamp=None, subject=None, **kwargs)[source]#

Defines the semantics for Approving something using a sequence of one or more Signoffs

An Approval Type (subclass of AbstractApproval) defines the business and presentation logic for a specific type of approval. The state of an Approval instance is persisted by an ApprovalStamp and its related Signets.

Approval Types are pure-code objects, not stored in DB, as they define application logic, not application data. An Approval Type defines:

  • how the Stamp is labelled and rendered,

  • what sequecne of signoffs is required to complete it;

  • what permission is required to revoke it,

  • define and register new Approval Types with factory method:: BaseApproval.register(...)

Default meta-data & services can be overridden by subclasses or passed to .register() factory Approval Types are registered in the signoffs.registry.approvals where they can be retrieved by id

Caution

Stamp records are stored in DB with a reference to Approval.id Be cautions not to change or delete id’s that are in-use.

Use a SigningOrder to define the sequence of Signgoffs required.

  • default signing order is sequential, ordered alphabetically, based on SignoffFields / attribute defined on the Approval

Tip

  • most Approvals will override signing_order

  • use ordering API on Approval instance rather than accessing signing_order directly!

Initialization

Construct an Approval instance backed by the given stamp or an instance of cls.stampModel(**kwargs) subject is optional: the object this approval is meant to approve - set by ApprovalField but otherwise unused.

id: str#

ā€˜approval.abstract’

unique identifier for type - used like FK, don’t mess with these!

stampModel: signoffs.core.approvals.stamp_type#

None

signoffsManager: type#

None

signing_order: signoffs.core.signing_order.SigningOrder#

None

logic: signoffs.core.approvals.ApprovalLogic#

None

status: signoffs.core.status.ApprovalStatus#

None

label: str = <Multiline-String>#
render: signoffs.core.renderers.ApprovalRenderer#

None

urls: signoffs.core.urls.ApprovalUrlsManager#

None

classmethod register(id, **kwargs)[source]#

Create, register, and return a new subclass of cls with overrides for given kwargs attributes

Standard mechanism to define new Approval Types, typically in my_app/models.py or my_app/approvals.py Usage:

MyApproval = AbstractApproval.register('my_appproval_type', label='Approve it!', ...)
classmethod validate()[source]#

Run any class validation that must pass before class can be registered. Invoked by registry.

classmethod get_stampModel()[source]#

Always use this accessor as the stampModel attribute may be an ā€œapp.Modelā€ label

classmethod get_stamp_queryset(prefetch=('signatories',))[source]#

Return a base (unfiltered) queryset of ALL Stamps for this Approval Type

classmethod create(**kwargs)[source]#

Create and return an approval backed by a new Stamp instance

property subject#

The object being approved, if provided. Sub-classes with stamp FK relations may want to override this to access the stamp related object. subject is set by model Fields for convenient access to owner obj, but value is not used by any core logic.

property slug#

A slugified version of the signoff id, for places where a unique identifier slug is required

property stamp_model#

return the signoff model for this type

get_new_stamp(**kwargs)[source]#

Get a new, unsaved stamp instance for this approval type

__str__()[source]#
__eq__(other)[source]#
__contains__(item)[source]#

Return True iff this approval’s signatories contains the item: signoff id, type, or user

property signoffs#

Return an ApprovalSignoffsManager for access to this approval’s signoff set Default implementation returns manager for signoffs backed by stamp’s signet_set manager. Concrete Approval Types may inject a custom signoffsManager for custom set of signoffs.

is_signed()[source]#

Return True iff this approval has at least one signoff

has_signed(user)[source]#

Return True iff given user is a signatory on this approval’s set of signoffs

has_signoff(signoff_id_or_type)[source]#

Return True iff this approval already has a signoff of the given signoff_type

is_signable(by_user=None)[source]#

Return True iff this approval is signable

can_sign(user, signoff=None)[source]#

return True iff the given user can sign given signoff on this approval, or any of the next signoffs in its signing order

ready_to_approve()[source]#

Return True iff this approval’s signing order is complete and ready to be approved

approve_if_ready(commit=True)[source]#

Approve and save this approval is it meets all ready conditions

approve(commit=True, **kwargs)[source]#

Approve this stamp. No permissions or signoff logic involved here - just force into approved state! Raises PermissionDenied if self.is_approved() – can’t approval an approved approval :-P kwargs passed directly to save() - use commit=False to approve without saving

is_revokable(by_user=None)[source]#

Return True iff this approval is in a state it could be revoked, optionally by given user

classmethod is_permitted_revoker(user)[source]#

Return True iff user has permission to revoke approvals of this type

can_revoke(user)[source]#

Return True iff this approval can be revoked by given user

revoke_if_permitted(user, **kwargs)[source]#

Revoke and save the approval is it meets all conditions for revocation

revoke(user, **kwargs)[source]#

Revoke this approval regardless of permissions or approval state - careful!

Prefer to use revoke_if_permitted to enforce business rules.

can_revoke_signoff(signoff, user)[source]#

Return True iff the given signoff can be revoked from this approval by given user

property signatories#

Return queryset of Signets representing the signatories on this approval

Default implementation simply returns Stamp’s ā€œreverseā€ signet_set manager. Concrete Approval Types can override to provide any sensible qs of signets.

property timestamp#

Return the timestamp approval was granted, None otherwise

has_signatories()[source]#

Return True iff this approval has any signatories

is_approved()[source]#

Return True iff this Approval is in an approved state

save(*args, **kwargs)[source]#

Attempt to save the Stamp of Approval, with the provided given associated data

classmethod has_object_relation()[source]#
is_complete()[source]#

Is this approval process complete and ready to be approved? Default implementation returns False if no signing order, True if the signing order is complete. Concrete Approval Types can override this method to customize conditions under which this approval is complete.

next_signoff_types(for_user=None)[source]#

Return list of next signoff type(s) (Signoff Type) required in this approval process.

Default impl returns next signoffs from the approval’s signing order or [] if no signing order is available. Concrete Approval Types can override this with custom business logic to provide signing order automation.

next_signoffs(for_user=None)[source]#

Return list of next signoff instance(s) required in this approval process.

Returns:

list[AbstractSignoff] where all(s.can_sign(for_user) for s in list)

If a user object is supplied, filter out instances not available to that user. Most applications will define custom business logic for ordering signoffs, restricting duplicate signs, etc. - ideally, use ApprovalLogic and SigningOrder to handle these, but this gives total control!

get_next_signoff(for_user=None)[source]#

Return the next available signoffs for given user, or None

Again, ideally define ApprovalLogic or SigningOrder rather than overriding behaviour here.

class signoffs.core.approvals.BaseApproval(stamp=None, subject=None, **kwargs)[source]#

Bases: signoffs.core.approvals.AbstractApproval

A base Approval Type to be used as base class or to register concrete Approval Types Concrete Types will require a concrete Stamp Model to back the approval.

Initialization

Construct an Approval instance backed by the given stamp or an instance of cls.stampModel(**kwargs) subject is optional: the object this approval is meant to approve - set by ApprovalField but otherwise unused.

id#

ā€˜signoffs.base-approval’

stampModel#

None

class signoffs.core.approvals.DefaultApprovalBusinessLogic(revoke_perm=None, revoke_method=None)[source]#

Defines the business logic for Approving and Revoking an Approval instance

Initialization

Override default actions, or leave parameter None to use class default

revoke_perm: signoffs.core.approvals.opt_str = <Multiline-String>#
revoke_method: Callable#

None

is_signable(approval, by_user=None)[source]#

Return True iff this approval is in a state it could be signed by the given user

Important

  • this is approval-level logic only – keep signoff-level rules in signoff.can_sign

  • does not determine if there is a signoff available to be signed, only about the state of this approval!

  • use can_sign to determine if there are any actual signoffs available to the user to be signed.

can_sign(approval, user, signoff=None)[source]#

Return True iff the given user can sign given signoff on this approval, or any of the next signoffs in its signing order

If a Signoff instance is provided, check that the user can sign this specific signoff.

ready_to_approve(approval)[source]#

Return True iff the approval’s signing order is complete and ready to be approved

approve_if_ready(approval, commit=True)[source]#

Approve and save the approval is it meets all ready conditions; return True iff this was done.

approve(approval, commit=True, **kwargs)[source]#

Approve the approval and save it’s Stamp.

No permissions or signoff completion logic involved here - just force into approved state!

  • Prefer to use approve_if_ready to enforce business rules. kwargs passed directly to save() - use commit=False to approve without saving

is_revokable(approval, by_user=None)[source]#

Return True iff this approval is in a state it could be revoked by the given user

Important

  • this is approval-level logic only – keep signoff-level rules in signoff.can_revoke

  • does not determine if there is a signoff available to be revoked, only about the state of this approval!

  • use can_revoke to determine if the approval is actually available to the user to be revoked.

is_permitted_revoker(approval_type, user)[source]#

return True iff user has permission to revoke approvals of given Type

can_revoke(approval, user)[source]#

Return True iff the approval can be revoked by given user

revoke_if_permitted(approval, user, reason='')[source]#

Revoke and save the approval if it meets all conditions for revocation.

Raises:

PermissionDenied – if not

revoke(approval, user, reason='')[source]#

Revoke the approval and save its Stamp.

No permissions or completion logic involved here - just force into revoked state! Prefer to use revoke_if_permitted to enforce business rules.

can_revoke_signoff(approval, signoff, user)[source]#

Return True iff the given signoff can be revoked from this approval by given user

Note

Default logic restricts revoke to the last signoff collected on unapproved approvals. Think carefully before overriding this restriction - users sign in-order and that often has meaning, even in cases where signoffs are collected purely ā€œin-parallelā€.

class signoffs.core.approvals.ApprovalLogic(revoke_perm=None, revoke_method=None)[source]#

Bases: signoffs.core.approvals.DefaultApprovalBusinessLogic

Public API: Alias for `DefaultApprovalBusinessLogic

Initialization

Override default actions, or leave parameter None to use class default

signoffs.contrib.approvals.approvals#

Some basic Approval Types backed by the Stamp model defined in this package.

Module Contents#

Classes#

ApprovalSignoff

An abstract, base Signoff Type backed by a ApprovalSignet - a Signet with a FK relation to an ApprovalStamp

SimpleApproval

A base Approval Type that can be used out-of-the-box for simple use-cases where any user can sign off Backed by signoffs.contrib.approvals.models.Stamp model.

IrrevokableApproval

A SimpleApproval that can never be revoked

Functions#

approval_signoff_form

Avoid circular imports that might arise from importing form before models are finished loading

API#

signoffs.contrib.approvals.approvals.approval_signoff_form()[source]#

Avoid circular imports that might arise from importing form before models are finished loading

class signoffs.contrib.approvals.approvals.ApprovalSignoff(signet=None, subject=None, **kwargs)[source]#

Bases: signoffs.core.signoffs.BaseSignoff

An abstract, base Signoff Type backed by a ApprovalSignet - a Signet with a FK relation to an ApprovalStamp

Initialization

Construct a Signoff instance backed by the given signet or an instance of cls.signetModel(**kwargs)

subject is optional: the object this signoff is signing off on - set by SignoffField but otherwise unused.

signetModel#

None

forms#

None

property subject#

Subject is the approval being signed off on.

property approval#

friendly name for subject

class signoffs.contrib.approvals.approvals.SimpleApproval(stamp=None, subject=None, **kwargs)#

Bases: signoffs.core.approvals.BaseApproval

A base Approval Type that can be used out-of-the-box for simple use-cases where any user can sign off Backed by signoffs.contrib.approvals.models.Stamp model.

Initialization

Construct an Approval instance backed by the given stamp or an instance of cls.stampModel(**kwargs) subject is optional: the object this approval is meant to approve - set by ApprovalField but otherwise unused.

stampModel#

None

label#

ā€˜Approve’

class signoffs.contrib.approvals.approvals.IrrevokableApproval(stamp=None, subject=None, **kwargs)#

Bases: signoffs.contrib.approvals.approvals.SimpleApproval

A SimpleApproval that can never be revoked

Initialization

Construct an Approval instance backed by the given stamp or an instance of cls.stampModel(**kwargs) subject is optional: the object this approval is meant to approve - set by ApprovalField but otherwise unused.

logic#

None