angee.proposals.models
Sealed proposal rounds and their structured response documents.
Round and Proposal are supporting coordination/document objects, not planning hierarchies. A Round owns disclosure and decision; a Proposal owns its response lifecycle. Direct relationship tuples are the seal: responder/editor tuples are verb-managed, while only Round audience roles are exposed through the declared record-share surface.
RoundOpeningPolicy
class RoundOpeningPolicy(models.TextChoices)Which responder-owned surfaces disclosure opens.
RoundStatus
class RoundStatus(models.TextChoices)Code-owned Round lifecycle.
RoundOutcome
class RoundOutcome(models.TextChoices)Terminal close outcomes.
ProposalState
class ProposalState(models.TextChoices)Single Proposal document lifecycle.
ProposalConfidence
class ProposalConfidence(models.TextChoices)Closed confidence vocabulary for comparison.
ImmutableFieldsMixin
class ImmutableFieldsMixin(models.Model)Reject identity/receipt changes except through an owning model verb.
Meta
class Meta()Django options for the addon-local invariant mixin.
allow_immutable_save
def allow_immutable_save(*field_names: str) -> NoneAllow the next save to change named immutable attnames.
save
def save(*args: Any, **kwargs: Any) -> NonePersist after comparing immutable facts with the committed row.
Round
class Round(ImmutableFieldsMixin, AuditMixin, ThreadedModelMixin,
AngeeDataModel)A bounded solicitation on one project or task; its chatter is shared.
Meta
class Meta()Django options for solicitation rounds.
__str__
def __str__() -> strReturn the Round's human name.
apply_create_defaults
def apply_create_defaults() -> Mapping[str, tuple[models.Model, ...]]Expose the exactly-one target to the unsaved-row create preflight.
clean
def clean() -> NoneValidate target, deadline, outcome, and receipt coherence.
save
def save(*args: Any, **kwargs: Any) -> NonePersist while keeping target/deadline and opened-policy facts coherent.
deletion_error
def deletion_error() -> str | NoneReturn why this Round cannot be deleted under the untouched-draft rule.
delete
def delete(*args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]Delete only a collecting Round whose proposals are untouched drafts.
open
def open() -> SelfOpen disclosure exactly once, locking Round before Proposal rows.
close
def close(
outcome: str | RoundOutcome,
*,
accepted: Iterable[models.Model] = (),
partial: Iterable[models.Model] = ()
) -> SelfClose with an exact decision over every submitted Proposal.
cancel
def cancel() -> SelfCancel a collecting or opened Round, preserving a null outcome.
transfer_facilitation
def transfer_facilitation(user: models.Model) -> SelfTransfer facilitation and reconcile every private track editor grant.
Topic
class Topic(ImmutableFieldsMixin, AuditMixin, AngeeDataModel)One stable, ordered comparison subject within a Round.
Meta
class Meta()Django options for ordered comparison topics.
__str__
def __str__() -> strReturn the topic name.
clean
def clean() -> NoneNormalize the immutable machine key.
save
def save(*args: Any, **kwargs: Any) -> NonePersist the normalized stable key.
ProposalManager
class ProposalManager(AngeeManager)Own replay-safe message capture into Proposal and Answer rows.
capture_from_message
def capture_from_message(message: models.Model,
round: models.Model,
party: models.Model | None = None) -> models.ModelInterpret one reply atomically and submit only after all rows succeed.
Proposal
class Proposal(ImmutableFieldsMixin, AuditMixin, AngeeDataModel)One responder's structured response; its private work lives on track.
Meta
class Meta()Django options for sealed response documents.
__str__
def __str__() -> strReturn a compact responder/party label.
clean
def clean() -> NoneValidate identity, money, timeframe, and lifecycle receipts.
save
def save(*args: Any, **kwargs: Any) -> NoneCreate shells only while collecting and reconcile verb-managed tuples.
deletion_error
def deletion_error() -> str | NoneReturn why this Proposal is no longer an untouched draft.
delete
def delete(*args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]Delete only an untouched draft shell.
submit
def submit() -> SelfSubmit while locking Round first and atomically revoking editor.
withdraw
def withdraw() -> SelfWithdraw one submitted Proposal under the Round-first lock order.
identify_party
def identify_party(party: models.Model) -> SelfSet the external identity once under Round-first row locks.
create_track
def create_track() -> models.ModelCreate the one private Project and grant facilitator plus responder editors.
publish_track
def publish_track() -> models.ModelPublish the private track to this Round's responders, idempotently.
TaskProposalContainment
class TaskProposalContainment(AngeeModel)Same-row save participant keeping unpublished proposal work private.
Meta
class Meta()Abstract zero-column donor folded into the composed Task model.
save
def save(*args: Any, **kwargs: Any) -> NoneReject shared queues for tasks on an unpublished proposal track.
Answer
class Answer(ImmutableFieldsMixin, AuditMixin, AngeeDataModel)One Proposal response aligned to one Topic in the same Round.
Meta
class Meta()Django options for topic-aligned answers.
clean
def clean() -> NoneReject an Answer whose Proposal and Topic belong to different Rounds.
__str__
def __str__() -> strReturn the topic-aligned response label.
Review
class Review(ImmutableFieldsMixin, AuditMixin, AngeeDataModel)One evaluator's private assessment of a Proposal.
Meta
class Meta()Django options for evaluator assessments.
__str__
def __str__() -> strReturn the reviewer/proposal assessment label.