コード例 #1
0
class StripeSourcePaymentStateMachine(BasePaymentStateMachine):

    model = StripeSourcePayment

    charged = State(_('charged'), 'charged')
    canceled = State(_('canceled'), 'canceled')
    disputed = State(_('disputed'), 'disputed')

    def has_charge_token(self):
        return bool(self.instance.charge_token)

    def is_not_refunded(self):
        return self.instance.status not in ['refunded', 'disputed']

    authorize = Transition(
        [BasePaymentStateMachine.new, charged],
        BasePaymentStateMachine.pending,
        name=_('Authorize'),
        automatic=True,
        effects=[RelatedTransitionEffect('donation', 'succeed')])

    succeed = Transition([
        BasePaymentStateMachine.new, BasePaymentStateMachine.pending, charged
    ],
                         BasePaymentStateMachine.succeeded,
                         name=_('Succeed'),
                         automatic=True,
                         effects=[
                             RelatedTransitionEffect('donation', 'succeed')
                         ])

    charge = Transition(BasePaymentStateMachine.new,
                        charged,
                        name=_('Charge'),
                        automatic=True,
                        conditions=[has_charge_token])

    cancel = Transition(BasePaymentStateMachine.new,
                        canceled,
                        name=_('Canceled'),
                        automatic=True,
                        effects=[RelatedTransitionEffect('donation', 'fail')])

    dispute = Transition(
        [
            BasePaymentStateMachine.new,
            BasePaymentStateMachine.succeeded,
        ],
        disputed,
        name=_('Dispute'),
        automatic=True,
        effects=[RelatedTransitionEffect('donation', 'refund')])
コード例 #2
0
class ContributionStateMachine(ModelStateMachine):
    new = State(_('new'), 'new', _("The user started a contribution"))
    succeeded = State(_('succeeded'), 'succeeded',
                      _("The contribution was successful."))
    failed = State(_('failed'), 'failed', _("The contribution failed."))

    def is_user(self, user):
        return self.instance.user == user

    initiate = Transition(EmptyState(),
                          new,
                          name=_('initiate'),
                          description=_('The contribution was created.'))
    fail = Transition(
        (
            new,
            succeeded,
            failed,
        ),
        failed,
        name=_('fail'),
        description=_(
            "The contribution failed. It will not be visible in reports."),
    )
コード例 #3
0
class ApplicantStateMachine(ContributionStateMachine):
    model = Applicant

    accepted = State(
        _('accepted'), 'accepted',
        _('The applicant was accepted and will join the activity.'))
    rejected = State(
        _('rejected'), 'rejected',
        _("The applicant was rejected and will not join the activity."))
    withdrawn = State(
        _('withdrawn'), 'withdrawn',
        _('The applicant withdrew and will no longer join the activity.'))
    no_show = State(_('no show'), 'no_show',
                    _('The applicant did not contribute to the activity.'))
    active = State(_('active'), 'active',
                   _('The applicant is currently working on the activity.'))

    def has_time_spent(self):
        """time spent is set"""
        return self.instance.time_spent

    def has_no_time_spent(self):
        """time spent is not set"""
        return not self.instance.time_spent

    def is_user(self, user):
        """is applicant"""
        return self.instance.user == user

    def is_activity_owner(self, user):
        """is activity manager or staff member"""
        return user.is_staff or self.instance.activity.owner == user

    def assignment_will_become_full(self):
        """task will be full"""
        activity = self.instance.activity
        return activity.capacity == len(activity.accepted_applicants) + 1

    def assignment_will_become_open(self):
        """task will not be full"""
        activity = self.instance.activity
        return activity.capacity == len(activity.accepted_applicants)

    def assignment_is_finished(self):
        """task is finished"""
        return self.instance.activity.end < timezone.now()

    def assignment_is_not_finished(self):
        "task is not finished"
        return not self.instance.activity.date < timezone.now()

    def assignment_will_be_empty(self):
        """task be empty"""
        return len(self.instance.activity.accepted_applicants) == 1

    def can_accept_applicants(self, user):
        """can accept applicants"""
        return user in [
            self.instance.activity.owner,
            self.instance.activity.initiative.activity_manager,
            self.instance.activity.initiative.owner
        ]

    def assignment_is_open(self):
        """task is open"""
        return self.instance.activity.status == ActivityStateMachine.open.value

    initiate = Transition(EmptyState(),
                          ContributionStateMachine.new,
                          name=_('Initiate'),
                          description=_("User applied to join the task."),
                          effects=[
                              NotificationEffect(AssignmentApplicationMessage),
                              FollowActivityEffect,
                              TransitionEffect(
                                  'succeed',
                                  conditions=[assignment_is_finished])
                          ])

    accept = Transition(
        [ContributionStateMachine.new, rejected],
        accepted,
        name=_('Accept'),
        description=_("Applicant was accepted."),
        automatic=False,
        permission=can_accept_applicants,
        effects=[
            TransitionEffect('succeed', conditions=[assignment_is_finished]),
            RelatedTransitionEffect('activity',
                                    'lock',
                                    conditions=[assignment_will_become_full]),
            RelatedTransitionEffect('activity',
                                    'succeed',
                                    conditions=[assignment_is_finished]),
            NotificationEffect(ApplicantAcceptedMessage)
        ])

    reaccept = Transition(ContributionStateMachine.succeeded,
                          accepted,
                          name=_('Accept'),
                          description=_("Applicant was accepted."),
                          automatic=True,
                          effects=[
                              RelatedTransitionEffect(
                                  'activity',
                                  'lock',
                                  conditions=[assignment_will_become_full]),
                              ClearTimeSpent,
                          ])

    reject = Transition([ContributionStateMachine.new, accepted],
                        rejected,
                        name=_('Reject'),
                        description=_("Applicant was rejected."),
                        automatic=False,
                        permission=can_accept_applicants,
                        effects=[
                            RelatedTransitionEffect('activity', 'reopen'),
                            NotificationEffect(ApplicantRejectedMessage),
                            UnFollowActivityEffect
                        ])

    withdraw = Transition(
        [ContributionStateMachine.new, accepted],
        withdrawn,
        name=_('Withdraw'),
        description=_(
            "Applicant withdrew and will no longer join the activity."),
        automatic=False,
        permission=is_user,
        hide_from_admin=True,
        effects=[UnFollowActivityEffect])

    reapply = Transition(
        [withdrawn, ContributionStateMachine.failed],
        ContributionStateMachine.new,
        name=_('Reapply'),
        description=_(
            "Applicant re-applies for the task after previously withdrawing."),
        automatic=False,
        conditions=[assignment_is_open],
        permission=ContributionStateMachine.is_user,
        effects=[
            FollowActivityEffect,
            NotificationEffect(AssignmentApplicationMessage)
        ])

    activate = Transition(
        [
            accepted,
            # ContributionStateMachine.new
        ],
        active,
        name=_('Activate'),
        description=_("Applicant starts to execute the task."),
        automatic=True)

    succeed = Transition(
        [accepted, active, ContributionStateMachine.new],
        ContributionStateMachine.succeeded,
        name=_('Succeed'),
        description=_("Applicant successfully completed the task."),
        automatic=True,
        effects=[SetTimeSpent])

    mark_absent = Transition(
        ContributionStateMachine.succeeded,
        no_show,
        name=_('Mark absent'),
        description=_(
            "Applicant did not contribute to the task and is marked absent."),
        automatic=False,
        permission=is_activity_owner,
        effects=[
            ClearTimeSpent,
            RelatedTransitionEffect(
                'activity',
                'cancel',
                conditions=[assignment_is_finished, assignment_will_be_empty]),
            UnFollowActivityEffect
        ])
    mark_present = Transition(
        no_show,
        ContributionStateMachine.succeeded,
        name=_('Mark present'),
        description=
        _("Applicant did contribute to the task, after first been marked absent."
          ),
        automatic=False,
        permission=is_activity_owner,
        effects=[
            SetTimeSpent,
            RelatedTransitionEffect('activity',
                                    'succeed',
                                    conditions=[assignment_is_finished]),
            FollowActivityEffect
        ])

    reset = Transition(
        [
            ContributionStateMachine.succeeded,
            accepted,
            ContributionStateMachine.failed,
        ],
        ContributionStateMachine.new,
        name=_('Reset'),
        description=_(
            "The applicant is reset to new after being successful or failed."),
        effects=[ClearTimeSpent])
コード例 #4
0
class AssignmentStateMachine(ActivityStateMachine):
    model = Assignment

    running = State(
        _('running'), 'running',
        _('The task is taking place and people can\'t apply any more.'))
    full = State(
        _('full'), 'full',
        _('The number of people needed is reached and people can\'t apply any more.'
          ))

    def should_finish(self):
        """end date has passed"""
        return self.instance.end and self.instance.end < timezone.now()

    def should_start(self):
        """start date has passed"""
        return self.instance.start and self.instance.start < timezone.now(
        ) and not self.should_finish()

    def has_deadline(self):
        """has a deadline"""
        return self.instance.end_date_type == 'deadline'

    def is_on_date(self):
        """takes place on a set date"""
        return self.instance.end_date_type == 'on_date'

    def should_open(self):
        """registration deadline is in the future"""
        return self.instance.start and self.instance.start >= timezone.now(
        ) and not self.should_finish()

    def has_accepted_applicants(self):
        """there are accepted applicants"""
        return len(self.instance.accepted_applicants) > 0

    def has_no_accepted_applicants(self):
        """there are no accepted applicants"""
        return len(self.instance.accepted_applicants) == 0

    def has_new_or_accepted_applicants(self):
        """there are accepted applicants"""
        return len(self.instance.accepted_applicants) > 0 or len(
            self.instance.new_applicants) > 0

    def has_no_new_or_accepted_applicants(self):
        """there are no accepted applicants"""
        return len(self.instance.accepted_applicants) == 0 and len(
            self.instance.new_applicants) == 0

    def is_not_full(self):
        """the task is not full"""
        return (self.instance.capacity and self.instance.capacity > len(
            self.instance.accepted_applicants))

    def is_full(self):
        """the task is full"""
        return (self.instance.capacity and self.instance.capacity <= len(
            self.instance.accepted_applicants))

    start = Transition([ActivityStateMachine.open, full],
                       running,
                       name=_('Start'),
                       description=_("Start the activity."),
                       automatic=True,
                       effects=[
                           RelatedTransitionEffect('accepted_applicants',
                                                   'activate'),
                       ])

    lock = Transition(
        [ActivityStateMachine.open],
        full,
        automatic=True,
        name=_('Fill'),
        description=_(
            "People can no longer apply. Triggered when the number of accepted people "
            "equals the number of people needed."),
    )

    auto_approve = Transition(
        [ActivityStateMachine.submitted, ActivityStateMachine.rejected],
        ActivityStateMachine.open,
        name=_('Approve'),
        automatic=True,
        description=_(
            "The task will be visible in the frontend and people can apply to "
            "the task."),
        effects=[
            RelatedTransitionEffect('organizer', 'succeed'),
            RelatedTransitionEffect('applicants', 'reset'),
            TransitionEffect(
                'expire',
                conditions=[should_finish, has_no_accepted_applicants]),
        ])

    reject = Transition(
        [
            ActivityStateMachine.draft, ActivityStateMachine.needs_work,
            ActivityStateMachine.submitted
        ],
        ActivityStateMachine.rejected,
        name=_('Reject'),
        description=
        _('Reject in case this task doesn\'t fit your program or the rules of the game. '
          'The activity owner will not be able to edit the task and it won\'t show up on '
          'the search page in the front end. The task will still be available in the '
          'back office and appear in your reporting.'),
        automatic=False,
        permission=ActivityStateMachine.is_staff,
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            NotificationEffect(AssignmentRejectedMessage),
        ])

    cancel = Transition(
        [
            full,
            running,
            ActivityStateMachine.succeeded,
            ActivityStateMachine.open,
        ],
        ActivityStateMachine.cancelled,
        name=_('Cancel'),
        description=
        _('Cancel if the task will not be executed. The activity manager will not be able '
          'to edit the task and it won\'t show up on the search page in the front end. The '
          'task will still be available in the back office and appear in your reporting.'
          ),
        automatic=False,
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            RelatedTransitionEffect('accepted_applicants', 'fail'),
            NotificationEffect(AssignmentCancelledMessage),
        ])

    expire = Transition(
        [
            ActivityStateMachine.submitted, ActivityStateMachine.open,
            ActivityStateMachine.succeeded
        ],
        ActivityStateMachine.cancelled,
        name=_("Expire"),
        description=
        _("The tasks didn\'t have any applicants before the deadline and is cancelled."
          ),
        effects=[
            NotificationEffect(AssignmentExpiredMessage),
        ])

    reopen = Transition(
        full,
        ActivityStateMachine.open,
        name=_('Reopen'),
        description=_(
            'People can apply to the task again. Triggered when the number of accepted people '
            'become less than the number of people needed.'),
        automatic=True)

    reschedule = Transition(
        [ActivityStateMachine.cancelled, ActivityStateMachine.succeeded],
        ActivityStateMachine.open,
        name=_('Reschedule'),
        description=_("Reschedule the activity for new sign-ups. "
                      "Triggered by a changing to a future date."),
        automatic=True,
        effects=[
            RelatedTransitionEffect('accepted_applicants', 'reaccept'),
        ])

    succeed = Transition(
        [
            ActivityStateMachine.open, full, running,
            ActivityStateMachine.cancelled
        ],
        ActivityStateMachine.succeeded,
        name=_('Succeed'),
        description=
        _('The task ends and the contributions are counted. Triggered when the task date passes.'
          ),
        automatic=True,
        effects=[
            RelatedTransitionEffect('accepted_applicants', 'succeed'),
            RelatedTransitionEffect('new_applicants', 'succeed'),
            NotificationEffect(AssignmentCompletedMessage)
        ])

    expire = Transition(
        ActivityStateMachine.open,
        ActivityStateMachine.cancelled,
        name=_('Expire'),
        description=
        _("The task didn't have any applicants before the deadline to apply and is cancelled."
          ),
        automatic=True,
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            NotificationEffect(AssignmentExpiredMessage),
        ])

    restore = Transition(
        [
            ActivityStateMachine.rejected,
            ActivityStateMachine.cancelled,
            ActivityStateMachine.deleted,
        ],
        ActivityStateMachine.needs_work,
        name=_("Restore"),
        automatic=False,
        description=_("Restore a cancelled, rejected or deleted task."),
        effects=[
            RelatedTransitionEffect('organizer', 'reset'),
            RelatedTransitionEffect('accepted_applicants', 'fail')
        ])
コード例 #5
0
class PayoutAccountStateMachine(ModelStateMachine):
    new = State(
        _('new'),
        'new',
        _("Payout account was created.")
    )
    pending = State(
        _('pending'),
        'pending',
        _("Payout account is pending verification.")
    )
    verified = State(
        _('verified'),
        'verified',
        _("Payout account has been verified.")
    )
    rejected = State(
        _('rejected'),
        'rejected',
        _("Payout account was rejected.")
    )
    incomplete = State(
        _('incomplete'),
        'incomplete',
        _("Payout account is missing information or documents.")
    )

    def can_approve(self, user=None):
        """is staff user"""
        return not user or user.is_staff

    def is_reviewed(self):
        """has been verified"""
        return self.instance.reviewed

    def is_unreviewed(self):
        """has not been verified"""
        return not self.instance.reviewed

    initiate = Transition(
        EmptyState(),
        new,
        name=_("Initiate"),
        description=_("Payout account has been created")
    )

    submit = Transition(
        [new, incomplete],
        pending,
        name=_('Submit'),
        description=_("Submit payout account for review."),
        automatic=False
    )

    verify = Transition(
        [new, incomplete, rejected, pending],
        verified,
        name=_('Verify'),
        description=_("Verify the payout account."),
        automatic=False,
        permission=can_approve,
        effects=[
            NotificationEffect(PayoutAccountVerified),
            RelatedTransitionEffect('external_accounts', 'verify')
        ]
    )

    reject = Transition(
        [new, incomplete, verified, pending],
        rejected,
        name=_('Reject'),
        description=_("Reject the payout account."),
        automatic=False,
        effects=[
            NotificationEffect(PayoutAccountRejected),
            RelatedTransitionEffect('external_accounts', 'reject')
        ]
    )

    set_incomplete = Transition(
        [new, pending, rejected, verified],
        incomplete,
        name=_('Set incomplete'),
        description=_("Mark the payout account as incomplete. The initiator will have to add more information."),
        automatic=False
    )
コード例 #6
0
class BankAccountStateMachine(ModelStateMachine):
    verified = State(
        _('verified'),
        'verified',
        _("Bank account is verified")
    )
    incomplete = State(
        _('incomplete'),
        'incomplete',
        _("Bank account details are missing or incorrect")
    )
    unverified = State(
        _('unverified'),
        'unverified',
        _("Bank account still needs to be verified")
    )
    rejected = State(
        _('rejected'),
        'rejected',
        _("Bank account is rejected")
    )

    initiate = Transition(
        EmptyState(),
        unverified,
        name=_("Initiate"),
        description=_("Bank account details are entered.")
    )

    request_changes = Transition(
        [verified, unverified],
        incomplete,
        name=_('Request changes'),
        description=_("Bank account is missing details"),
        automatic=False
    )

    reject = Transition(
        [verified, unverified, incomplete],
        rejected,
        name=_('Reject'),
        description=_("Reject bank account"),
        automatic=False,
        effects=[
            RelatedTransitionEffect(
                'connect_account',
                'reject',
                description='Reject connected KYC account'
            )
        ]
    )

    verify = Transition(
        [incomplete, unverified],
        verified,
        name=_('Verify'),
        description=_("Verify that the bank account is complete."),
        automatic=False,
        effects=[
            SubmitConnectedActivitiesEffect,
            RelatedTransitionEffect(
                'connect_account',
                'verify',
                description='Verify connected KYC account'
            )
        ]
    )
コード例 #7
0
class PayoutStateMachine(ModelStateMachine):
    model = Payout

    new = State(
        _('new'),
        'new',
        _("Payout has been created")
    )
    approved = State(
        _('approved'),
        'approved',
        _("Payout has been approved and send to the payout app.")
    )
    scheduled = State(
        _('scheduled'),
        'scheduled',
        _("Payout has been received by the payout app.")
    )
    started = State(
        _('started'),
        'started',
        _("Payout was started.")
    )
    succeeded = State(
        _('succeeded'),
        'succeeded',
        _("Payout was completed successfully.")
    )
    failed = State(
        _('failed'),
        'failed',
        _("Payout failed.")
    )

    initiate = Transition(
        EmptyState(),
        new,
        name=_("Initiate"),
        description=_("Create the payout")
    )

    approve = Transition(
        [new, approved],
        approved,
        name=_('Approve'),
        description=_("Approve the payout so it will be scheduled for execution."),
        automatic=False,
        effects=[
            SubmitPayoutEffect,
            SetDateEffect('date_approved')
        ]
    )

    schedule = Transition(
        AllStates(),
        scheduled,
        name=_('Schedule'),
        description=_("Schedule payout. Triggered by payout app."),
        automatic=True,
        effects=[
            ClearPayoutDatesEffect
        ]
    )

    start = Transition(
        AllStates(),
        started,
        name=_('Start'),
        description=_("Start payout. Triggered by payout app."),
        automatic=True,
        effects=[
            SetDateEffect('date_started')
        ]
    )

    reset = Transition(
        AllStates(),
        new,
        name=_('Reset'),
        description=_("Payout was rejected by the payout app. "
                      "Adjust information as needed an approve the payout again."),
        automatic=True,
        effects=[
            ClearPayoutDatesEffect
        ]
    )

    succeed = Transition(
        AllStates(),
        succeeded,
        name=_('Succeed'),
        description=_("Payout was successful. Triggered by payout app."),
        automatic=True,
        effects=[
            SetDateEffect('date_completed')
        ]
    )

    fail = Transition(
        AllStates(),
        failed,
        name=_('Fail'),
        description=_("Payout was not successful. "
                      "Contact support to resolve the issue."),
        automatic=True,
    )
コード例 #8
0
class BasePaymentStateMachine(ModelStateMachine):
    new = State(
        _('new'),
        'new',
        _("Payment was started.")
    )
    pending = State(
        _('pending'),
        'pending',
        _("Payment is authorised and will probably succeed shortly.")
    )
    succeeded = State(
        _('succeeded'),
        'succeeded',
        _("Payment is successful.")
    )
    failed = State(
        _('failed'),
        'failed',
        _("Payment failed.")
    )
    refunded = State(
        _('refunded'),
        'refunded',
        _("Payment was refunded.")
    )
    refund_requested = State(
        _('refund requested'),
        'refund_requested',
        _("Platform requested the payment to be refunded. Waiting for payment provider the confirm the refund")
    )

    def donation_not_refunded(self):
        """donation doesn't have status refunded or activity refunded"""
        return self.instance.donation.status not in [
            DonationStateMachine.refunded.value,
            DonationStateMachine.activity_refunded.value,
        ]

    initiate = Transition(
        EmptyState(),
        new,
        name=_("Initiate"),
        description=_("Payment started.")
    )

    authorize = Transition(
        [new],
        pending,
        name=_('Authorise'),
        description=_("Payment has been authorized."),
        automatic=True,
        effects=[
            RelatedTransitionEffect('donation', 'succeed')
        ]
    )

    succeed = Transition(
        [new, pending, failed, refund_requested],
        succeeded,
        name=_('Succeed'),
        description=_("Payment has been completed."),
        automatic=True,
        effects=[
            RelatedTransitionEffect('donation', 'succeed')
        ]
    )

    fail = Transition(
        AllStates(),
        failed,
        name=_('Fail'),
        description=_("Payment failed."),
        automatic=True,
        effects=[
            RelatedTransitionEffect('donation', 'fail')
        ]
    )

    request_refund = Transition(
        succeeded,
        refund_requested,
        name=_('Request refund'),
        description=_("Request to refund the payment."),
        automatic=False,
        effects=[
            RefundPaymentAtPSPEffect
        ]
    )

    refund = Transition(
        [
            new,
            succeeded,
            refund_requested
        ],
        refunded,
        name=_('Refund'),
        description=_("Payment was refunded."),
        automatic=True,
        effects=[
            RelatedTransitionEffect(
                'donation', 'refund',
                conditions=[
                    donation_not_refunded
                ]
            ),
        ]
    )
コード例 #9
0
class DonationStateMachine(ContributionStateMachine):
    model = Donation
    refunded = State(
        _('refunded'),
        'refunded',
        _("The contribution was refunded.")
    )
    activity_refunded = State(
        _('activity refunded'),
        'activity_refunded',
        _("The contribution was refunded because the activity refunded.")
    )

    def is_successful(self):
        """donation is successful"""
        return self.instance.status == ContributionStateMachine.succeeded

    succeed = Transition(
        [
            ContributionStateMachine.new,
            ContributionStateMachine.failed
        ],
        ContributionStateMachine.succeeded,
        name=_('Succeed'),
        description=_("The donation has been completed"),
        automatic=True,
        effects=[
            NotificationEffect(DonationSuccessActivityManagerMessage),
            NotificationEffect(DonationSuccessDonorMessage),
            GenerateDonationWallpostEffect,
            FollowActivityEffect,
            UpdateFundingAmountsEffect
        ]
    )

    fail = Transition(
        [
            ContributionStateMachine.new,
            ContributionStateMachine.succeeded
        ],
        ContributionStateMachine.failed,
        name=_('Fail'),
        description=_("The donation failed."),
        automatic=True,
        effects=[
            RemoveDonationWallpostEffect,
            UpdateFundingAmountsEffect,
            RemoveDonationFromPayoutEffect
        ]
    )

    refund = Transition(
        [
            ContributionStateMachine.new,
            ContributionStateMachine.succeeded,
        ],
        refunded,
        name=_('Refund'),
        description=_("Refund this donation."),
        automatic=True,
        effects=[
            RemoveDonationWallpostEffect,
            UnFollowActivityEffect,
            UpdateFundingAmountsEffect,
            RemoveDonationFromPayoutEffect,
            NotificationEffect(DonationRefundedDonorMessage)
        ]
    )

    activity_refund = Transition(
        ContributionStateMachine.succeeded,
        activity_refunded,
        name=_('Activity refund'),
        description=_("Refund the donation, because the entire activity will be refunded."),
        automatic=True,
        effects=[
            RelatedTransitionEffect('payment', 'request_refund'),
            NotificationEffect(DonationActivityRefundedDonorMessage)
        ]
    )
コード例 #10
0
class FundingStateMachine(ActivityStateMachine):
    model = Funding

    partially_funded = State(
        _('partially funded'),
        'partially_funded',
        _("The campaign has ended and received donations but didn't reach the target.")
    )
    refunded = State(
        _('refunded'),
        'refunded',
        _("The campaign has ended and all donations have been refunded.")
    )
    cancelled = State(
        _('cancelled'),
        'cancelled',
        _("The activity has ended without any donations.")
    )

    def should_finish(self):
        """the deadline has passed"""
        return self.instance.deadline and self.instance.deadline < timezone.now()

    def deadline_in_future(self):
        """the deadline is in the future"""
        if self.instance.deadline:
            return self.instance.deadline > timezone.now()
        return bool(self.instance.duration)

    def target_reached(self):
        """target amount has been reached (100% or more)"""
        if not self.instance.target:
            return False
        return self.instance.amount_raised >= self.instance.target

    def target_not_reached(self):
        """target amount has not been reached (less then 100%, but more then 0)"""
        return self.instance.amount_raised.amount and self.instance.amount_raised < self.instance.target

    def no_donations(self):
        """no (successful) donations have been made"""
        return not self.instance.donations.filter(status='succeeded').count()

    def without_approved_payouts(self):
        """hasn't got approved payouts"""
        return not self.instance.payouts.exclude(status__in=['new', 'failed']).count()

    def can_approve(self, user):
        """user has the permission to approve (staff member)"""
        return user.is_staff

    def psp_allows_refunding(self):
        """PSP allows refunding through their API"""
        return self.instance.bank_account and \
            self.instance.bank_account.provider_class and \
            self.instance.bank_account.provider_class.refund_enabled

    submit = Transition(
        [ActivityStateMachine.draft, ActivityStateMachine.needs_work],
        ActivityStateMachine.submitted,
        automatic=False,
        name=_('Submit'),
        description=_('The campaign will be submitted for review.'),
        conditions=[
            ActivityStateMachine.is_complete,
            ActivityStateMachine.is_valid,
            ActivityStateMachine.initiative_is_submitted
        ],
    )

    approve = Transition(
        [
            ActivityStateMachine.needs_work,
            ActivityStateMachine.submitted
        ],
        ActivityStateMachine.open,
        name=_('Approve'),
        description=_('The campaign will be visible in the frontend and people can donate.'),
        automatic=False,
        permission=can_approve,
        conditions=[
            ActivityStateMachine.initiative_is_approved,
            ActivityStateMachine.is_valid,
            ActivityStateMachine.is_complete
        ],
        effects=[
            RelatedTransitionEffect('organizer', 'succeed'),
            SetDateEffect('started'),
            SetDeadlineEffect,
            TransitionEffect(
                'expire',
                conditions=[should_finish]
            ),
            NotificationEffect(FundingApprovedMessage)
        ]
    )

    cancel = Transition(
        [
            ActivityStateMachine.open,
        ],
        cancelled,
        name=_('Cancel'),
        description=_(
            'Cancel if the campaign will not be executed. The activity manager '
            'will not be able to edit the campaign and it won\'t show up on the '
            'search page in the front end. The campaign will still be available '
            'in the back office and appear in your reporting.'
        ),
        automatic=False,
        conditions=[
            no_donations
        ],
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            NotificationEffect(FundingCancelledMessage)
        ]
    )

    request_changes = Transition(
        [
            ActivityStateMachine.submitted
        ],
        ActivityStateMachine.needs_work,
        name=_('Needs work'),
        description=_(
            "The status of the campaign will be set to 'Needs work'. The activity manager "
            "can edit and resubmit the campaign. Don't forget to inform the activity "
            "manager of the necessary adjustments."
        ),
        automatic=False,
        permission=can_approve
    )

    reject = Transition(
        [
            ActivityStateMachine.submitted,
            ActivityStateMachine.draft,
            ActivityStateMachine.needs_work,
        ],
        ActivityStateMachine.rejected,
        name=_('Reject'),
        description=_(
            "Reject in case this campaign doesn\'t fit your program or the rules of the game. "
            "The activity manager will not be able to edit the campaign and it won\'t show up "
            "on the search page in the front end. The campaign will still be available in the "
            "back office and appear in your reporting."
        ),
        automatic=False,
        conditions=[
            no_donations
        ],
        permission=ActivityStateMachine.is_staff,
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            NotificationEffect(FundingRejectedMessage)
        ]
    )

    expire = Transition(
        [
            ActivityStateMachine.open,
        ],
        ActivityStateMachine.cancelled,
        name=_('Expire'),
        description=_("The campaign didn't receive any donations before the deadline and is cancelled."),
        automatic=True,
        conditions=[
            no_donations,
        ],
        effects=[
            NotificationEffect(FundingExpiredMessage),
            RelatedTransitionEffect('organizer', 'fail'),
        ]
    )

    extend = Transition(
        [
            ActivityStateMachine.succeeded,
            partially_funded,
            ActivityStateMachine.cancelled,
        ],
        ActivityStateMachine.open,
        name=_('Extend'),
        description=_("The campaign will be extended and can receive more donations."),
        automatic=True,
        conditions=[
            without_approved_payouts,
            deadline_in_future
        ],
        effects=[
            DeletePayoutsEffect,
            NotificationEffect(FundingExtendedMessage)
        ]
    )

    succeed = Transition(
        [
            ActivityStateMachine.open,
            partially_funded
        ],
        ActivityStateMachine.succeeded,
        name=_('Succeed'),
        description=_(
            "The campaign ends and received donations can be payed out. Triggered when "
            "the deadline passes."
        ),
        automatic=True,
        effects=[
            GeneratePayoutsEffect,
            NotificationEffect(FundingRealisedOwnerMessage)
        ]
    )

    recalculate = Transition(
        [
            ActivityStateMachine.succeeded,
            partially_funded
        ],
        ActivityStateMachine.succeeded,
        name=_('Recalculate'),
        description=_("The amount of donations received has changed and the payouts will be recalculated."),
        automatic=False,
        conditions=[
            target_reached
        ],
        effects=[
            GeneratePayoutsEffect
        ]
    )

    partial = Transition(
        [
            ActivityStateMachine.open,
            ActivityStateMachine.succeeded
        ],
        partially_funded,
        name=_('Partial'),
        description=_("The campaign ends but the target isn't reached."),
        automatic=True,
        effects=[
            GeneratePayoutsEffect,
            NotificationEffect(FundingPartiallyFundedMessage)
        ]
    )

    refund = Transition(
        [
            ActivityStateMachine.succeeded,
            partially_funded
        ],
        refunded,
        name=_('Refund'),
        description=_("The campaign will be refunded and all donations will be returned to the donors."),
        automatic=False,
        conditions=[
            psp_allows_refunding,
            without_approved_payouts
        ],
        effects=[
            RelatedTransitionEffect('donations', 'activity_refund'),
            DeletePayoutsEffect,
            NotificationEffect(FundingRefundedMessage)
        ]
    )
コード例 #11
0
class ActivityStateMachine(ModelStateMachine):
    draft = State(
        _('draft'), 'draft',
        _('The activity has been created, but not yet completed. The activity manager is still editing the activity.'
          ))
    submitted = State(
        _('submitted'), 'submitted',
        _('The activity is ready to go online once the initiative has been approved.'
          ))
    needs_work = State(
        _('needs work'), 'needs_work',
        _('The activity has been submitted but needs adjustments in order to be approved.'
          ))
    rejected = State(
        _('rejected'), 'rejected',
        _('The activity doesn\'t fit the program or the rules of the game. '
          'The activity won\'t show up on the search page in the front end, '
          'but does count in the reporting. The activity cannot be edited by the activity manager.'
          ))
    deleted = State(
        _('deleted'), 'deleted',
        _('The activity is not visible in the frontend and does not count in the reporting. '
          'The activity cannot be edited by the activity manager.'))
    cancelled = State(
        _('cancelled'), 'cancelled',
        _('The activity is not executed. The activity won\'t show up on the search page '
          'in the front end, but does count in the reporting. The activity cannot be '
          'edited by the activity manager.'))
    open = State(_('open'), 'open',
                 _('The activity is accepting new contributions.'))
    succeeded = State(_('succeeded'), 'succeeded',
                      _('The activity has ended successfully.'))

    def is_complete(self):
        """all required information has been submitted"""
        return not list(self.instance.required)

    def is_valid(self):
        """all fields passed validation and are correct"""
        return not list(self.instance.errors)

    def initiative_is_approved(self):
        """the initiative has been approved"""
        return self.instance.initiative.status == 'approved'

    def initiative_is_submitted(self):
        """the initiative has been submitted"""
        return self.instance.initiative.status in ('submitted', 'approved')

    def initiative_is_not_approved(self):
        """the initiative has not yet been approved"""
        return not self.initiative_is_approved()

    def is_staff(self, user):
        """user is a staff member"""
        return user.is_staff

    def is_owner(self, user):
        """user is the owner"""
        return user == self.instance.owner

    initiate = Transition(EmptyState(),
                          draft,
                          name=_('Start'),
                          description=_('The acivity will be created.'),
                          effects=[CreateOrganizer])

    auto_submit = Transition(
        [
            draft,
            needs_work,
        ],
        submitted,
        description=_('The acivity will be submitted for review.'),
        automatic=True,
        name=_('Submit'),
        conditions=[is_complete, is_valid],
    )

    submit = Transition(
        [
            draft,
            needs_work,
        ],
        submitted,
        description=_('Submit the activity for approval.'),
        automatic=False,
        name=_('Submit'),
        conditions=[is_complete, is_valid, initiative_is_submitted],
        effects=[
            TransitionEffect('auto_approve',
                             conditions=[initiative_is_approved])
        ])

    reject = Transition(
        [draft, needs_work, submitted],
        rejected,
        name=_('Reject'),
        description=
        _('Reject in case this acivity doesn\'t fit your program or the rules of the game. '
          'The activity manager will not be able to edit the activity and it won\'t show up '
          'on the search page in the front end. The activity will still be available in the '
          'back office and appear in your reporting.'),
        automatic=False,
        permission=is_staff,
        effects=[RelatedTransitionEffect('organizer', 'fail')])

    cancel = Transition([
        open,
        succeeded,
    ],
                        cancelled,
                        name=_('Cancel'),
                        description=_('Cancel the activity.'),
                        automatic=False,
                        effects=[RelatedTransitionEffect('organizer', 'fail')])

    restore = Transition(
        [rejected, cancelled, deleted],
        needs_work,
        name=_('Restore'),
        description=_(
            'The status of the activity is set to "Needs work". The activity manager can edit '
            'the activity again.'),
        automatic=False,
        permission=is_staff,
        effects=[RelatedTransitionEffect('organizer', 'reset')])

    delete = Transition(
        [draft],
        deleted,
        name=_('Delete'),
        automatic=False,
        permission=is_owner,
        hide_from_admin=True,
        description=_(
            'Delete the activity if you don\'t want it to appear in your reporting. '
            'The activity will still be available in the back office.'),
        effects=[RelatedTransitionEffect('organizer', 'fail')])

    succeed = Transition(
        open,
        succeeded,
        name=_('Succeed'),
        automatic=True,
    )
コード例 #12
0
class EventStateMachine(ActivityStateMachine):
    model = Event

    def is_full(self):
        "the event is full"
        return self.instance.capacity == len(self.instance.participants)

    def is_not_full(self):
        "the event is not full"
        return self.instance.capacity > len(self.instance.participants)

    def should_finish(self):
        "the end time has passed"
        return self.instance.current_end and self.instance.current_end < timezone.now(
        )

    def should_start(self):
        "the start time has passed"
        return self.instance.start and self.instance.start < timezone.now(
        ) and not self.should_finish()

    def should_open(self):
        "the start time has not passed"
        return self.instance.start and self.instance.start > timezone.now()

    def has_participants(self):
        """there are participants"""
        return len(self.instance.participants) > 0

    def has_no_participants(self):
        """there are no participants"""
        return len(self.instance.participants) == 0

    full = State(_('full'), 'full', _('Submit the activity for approval.'))
    running = State(
        _('running'), 'running',
        _('The event is taking place and people can\'t join any more.'))

    submit = Transition(
        [
            ActivityStateMachine.draft,
            ActivityStateMachine.needs_work,
        ],
        ActivityStateMachine.submitted,
        description=_('Submit the activity for approval.'),
        automatic=False,
        name=_('Submit'),
        conditions=[
            ActivityStateMachine.is_complete, ActivityStateMachine.is_valid,
            ActivityStateMachine.initiative_is_submitted
        ],
        effects=[
            TransitionEffect('auto_approve',
                             conditions=[
                                 ActivityStateMachine.initiative_is_approved,
                                 should_open
                             ]),
            TransitionEffect('expire',
                             conditions=[should_start, has_no_participants]),
            TransitionEffect('expire',
                             conditions=[should_finish, has_no_participants]),
            TransitionEffect('succeed',
                             conditions=[should_finish, has_participants]),
        ])

    auto_approve = Transition(
        [ActivityStateMachine.submitted, ActivityStateMachine.rejected],
        ActivityStateMachine.open,
        name=_('Approve'),
        automatic=True,
        description=
        _("The event will be visible in the frontend and people can join the event."
          ),
        effects=[
            RelatedTransitionEffect('organizer', 'succeed'),
            TransitionEffect('expire',
                             conditions=[should_start, has_no_participants]),
            TransitionEffect('expire',
                             conditions=[should_finish, has_no_participants]),
            TransitionEffect('succeed',
                             conditions=[should_finish, has_participants]),
        ])

    cancel = Transition(
        [
            ActivityStateMachine.open,
            running,
            full,
            ActivityStateMachine.succeeded,
        ],
        ActivityStateMachine.cancelled,
        name=_('Cancel'),
        description=
        _('Cancel if the event will not be executed. The activity manager will not be '
          'able to edit the event and it won\'t show up on the search page in the front end. '
          'The event will still be available in the back office and appear in your reporting.'
          ),
        automatic=False,
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            RelatedTransitionEffect('participants', 'fail'),
            NotificationEffect(EventCancelledMessage),
        ])

    lock = Transition(
        [ActivityStateMachine.open, ActivityStateMachine.succeeded],
        full,
        name=_("Lock"),
        description=
        _("People can no longer join the event. Triggered when the attendee limit is reached."
          ))

    reopen = Transition(
        full,
        ActivityStateMachine.open,
        name=_("Reopen"),
        description=_(
            "People can join the event again. Triggered when the number of attendees become "
            "less than the attendee limit."))

    reschedule = Transition(
        [
            running, ActivityStateMachine.cancelled,
            ActivityStateMachine.succeeded
        ],
        ActivityStateMachine.open,
        name=_("Reschedule"),
        description=_(
            "People can join the event again, because the date has changed."),
        effects=[RelatedTransitionEffect('participants', 'reset')])

    start = Transition([ActivityStateMachine.open, full],
                       running,
                       name=_("Start"),
                       description=_("Start the event."))

    expire = Transition(
        [
            ActivityStateMachine.submitted, ActivityStateMachine.open,
            ActivityStateMachine.succeeded
        ],
        ActivityStateMachine.cancelled,
        name=_("Expire"),
        description=
        _("The event didn\'t have any attendees before the start time and is cancelled."
          ),
        effects=[
            NotificationEffect(EventExpiredMessage),
        ])

    succeed = Transition(
        [
            full, running, ActivityStateMachine.open,
            ActivityStateMachine.submitted, ActivityStateMachine.rejected,
            ActivityStateMachine.cancelled
        ],
        ActivityStateMachine.succeeded,
        name=_("Succeed"),
        description=_(
            "The event ends and the contributions are counted. Triggered when the event "
            "end time passes."),
        effects=[
            NotificationEffect(EventSucceededOwnerMessage),
            RelatedTransitionEffect('participants', 'succeed')
        ])

    reject = Transition(
        [
            ActivityStateMachine.draft, ActivityStateMachine.needs_work,
            ActivityStateMachine.submitted
        ],
        ActivityStateMachine.rejected,
        name=_('Reject'),
        description=
        _('Reject in case this event doesn\'t fit your program or the rules of the game. '
          'The activity owner will not be able to edit the event and it won\'t show up on '
          'the search page in the front end. The event will still be available in the '
          'back office and appear in your reporting.'),
        automatic=False,
        permission=ActivityStateMachine.is_staff,
        effects=[
            RelatedTransitionEffect('organizer', 'fail'),
            NotificationEffect(EventRejectedOwnerMessage),
        ])

    restore = Transition(
        [
            ActivityStateMachine.rejected,
            ActivityStateMachine.cancelled,
            ActivityStateMachine.deleted,
        ],
        ActivityStateMachine.needs_work,
        name=_("Restore"),
        automatic=False,
        description=_(
            "The status of the event is set to 'needs work'. The activity owner can edit "
            "the event again."),
        effects=[RelatedTransitionEffect('contributions', 'reset')])
コード例 #13
0
class ParticipantStateMachine(ContributionStateMachine):
    model = Participant

    withdrawn = State(
        _('withdrawn'), 'withdrawn',
        _("The participant withdrew and will no longer attend the activity"))
    rejected = State(_('rejected'), 'rejected',
                     _("The participant was rejected and will not attend."))
    no_show = State(
        _('no show'), 'no_show',
        _("The participant didn't attend the event and was marked absent."))
    new = State(_('Joined'), 'new',
                _("The participant signed up for the event."))

    def is_user(self, user):
        """is the participant"""
        return self.instance.user == user

    def is_activity_owner(self, user):
        """is the activity manager or a staff member"""
        return user.is_staff or self.instance.activity.owner == user

    def event_will_become_full(self):
        "event will be full"
        activity = self.instance.activity
        return activity.capacity == len(activity.participants) + 1

    def event_will_become_open(self):
        "event will not be full"
        activity = self.instance.activity
        return activity.capacity == len(activity.participants)

    def event_is_finished(self):
        "event is finished"
        return self.instance.activity.current_end < timezone.now()

    def event_is_not_finished(self):
        "event is not finished"
        return not self.instance.activity.start < timezone.now()

    def event_will_be_empty(self):
        "event will be empty"
        return self.instance.activity.participants.exclude(
            id=self.instance.id).count() == 0

    initiate = Transition(
        EmptyState(),
        ContributionStateMachine.new,
        name=_("Join"),
        description=_(
            "Participant is created. User signs up for the activity."),
        effects=[
            TransitionEffect('succeed', conditions=[event_is_finished]),
            RelatedTransitionEffect('activity',
                                    'lock',
                                    conditions=[event_will_become_full]),
            RelatedTransitionEffect('activity',
                                    'succeed',
                                    conditions=[event_is_finished]),
            NotificationEffect(ParticipantApplicationManagerMessage),
            NotificationEffect(ParticipantApplicationMessage),
            FollowActivityEffect,
        ])
    withdraw = Transition(
        ContributionStateMachine.new,
        withdrawn,
        name=_('Withdraw'),
        description=_("Participant withdraws from the activity."),
        automatic=False,
        permission=is_user,
        effects=[
            RelatedTransitionEffect('activity',
                                    'reopen',
                                    conditions=[event_will_become_open]),
            UnFollowActivityEffect
        ])
    reapply = Transition(
        withdrawn,
        ContributionStateMachine.new,
        name=_('Join again'),
        description=
        _("Participant signs up for the activity again, after previously withdrawing."
          ),
        automatic=False,
        permission=is_user,
        effects=[
            TransitionEffect('succeed', conditions=[event_is_finished]),
            RelatedTransitionEffect('activity',
                                    'lock',
                                    conditions=[event_will_become_full]),
            NotificationEffect(ParticipantApplicationManagerMessage),
            NotificationEffect(ParticipantApplicationMessage),
            FollowActivityEffect
        ])
    reject = Transition(ContributionStateMachine.new,
                        rejected,
                        automatic=False,
                        name=_('Reject'),
                        description=_("Participant is rejected."),
                        effects=[
                            RelatedTransitionEffect('activity', 'reopen'),
                            NotificationEffect(ParticipantRejectedMessage),
                            UnFollowActivityEffect
                        ],
                        permission=is_activity_owner)

    accept = Transition(
        rejected,
        ContributionStateMachine.new,
        name=_('Accept'),
        description=_("Accept a participant after previously being rejected."),
        automatic=False,
        effects=[
            TransitionEffect('succeed', conditions=[event_is_finished]),
            RelatedTransitionEffect('activity',
                                    'lock',
                                    conditions=[event_will_become_full]),
            NotificationEffect(ParticipantApplicationMessage),
            FollowActivityEffect
        ],
        permission=is_activity_owner)

    mark_absent = Transition(
        ContributionStateMachine.succeeded,
        no_show,
        name=_('Mark absent'),
        description=
        _("The participant didn't show up at the activity and is marked absent."
          ),
        automatic=False,
        permission=is_activity_owner,
        effects=[
            ResetTimeSpent,
            RelatedTransitionEffect(
                'activity',
                'expire',
                conditions=[event_is_finished, event_will_be_empty]),
            UnFollowActivityEffect
        ])
    mark_present = Transition(
        no_show,
        ContributionStateMachine.succeeded,
        name=_('Mark present'),
        description=_(
            "The participant showed up, after previously marked absent."),
        automatic=False,
        permission=is_activity_owner,
        effects=[
            SetTimeSpent,
            RelatedTransitionEffect('activity',
                                    'succeed',
                                    conditions=[event_is_finished]),
            FollowActivityEffect
        ])

    succeed = Transition(
        ContributionStateMachine.new,
        ContributionStateMachine.succeeded,
        name=_('Succeed'),
        description=_(
            "The participant successfully took part in the activity."),
        effects=[
            SetTimeSpent,
            RelatedTransitionEffect('activity',
                                    'succeed',
                                    conditions=[event_is_finished])
        ])

    reset = Transition(
        [
            ContributionStateMachine.succeeded,
            ContributionStateMachine.failed,
        ],
        ContributionStateMachine.new,
        name=_('Reset'),
        description=_(
            "The participant is reset to new after being successful or failed."
        ),
        effects=[ResetTimeSpent, FollowActivityEffect])

    fail = Transition(
        (
            ContributionStateMachine.new,
            ContributionStateMachine.succeeded,
            ContributionStateMachine.failed,
        ),
        ContributionStateMachine.failed,
        name=_('fail'),
        description=_(
            "The contribution failed. It will not be visible in reports."),
        effects=[ResetTimeSpent, UnFollowActivityEffect])
コード例 #14
0
class ReviewStateMachine(ModelStateMachine):
    field = 'status'
    model = Initiative

    draft = State(
        _('draft'),
        'draft',
        _('The initiative has been created and is being worked on.')
    )
    submitted = State(
        _('submitted'),
        'submitted',
        _('The initiative has been submitted and is ready to be reviewed.')
    )
    needs_work = State(
        _('needs work'),
        'needs_work',
        _('The initiative has been submitted but needs adjustments in order to be approved.')
    )
    rejected = State(
        _('rejected'),
        'rejected',
        _("The initiative doesn't fit the program or the rules of the game. "
          "The initiative won't show up on the search page in the front end, "
          "but does count in the reporting. "
          "The initiative cannot be edited by the initiator.")
    )
    cancelled = State(
        _('cancelled'),
        'cancelled',
        _("The initiative is not executed. "
          "The initiative won't show up on the search page in the front end, "
          "but does count in the reporting. "
          "The initiative cannot be edited by the initiator.")
    )
    deleted = State(
        _('deleted'),
        'deleted',
        _('The initiative is not visible in the frontend and does not count in the reporting. '
          'The initiative cannot be edited by the initiator.')
    )
    approved = State(
        _('approved'),
        'approved',
        _('The initiative is visible in the frontend and completed activities are open for contributions. '
          'All activities, except the crowdfunding campaigns, '
          'that will be completed at a later stage, '
          'will also be automatically opened up for contributions. '
          'The crowdfunding campaigns must be approved separately.')
    )

    def is_complete(self):
        """The initiative is complete"""
        if self.instance.organization and list(self.instance.organization.required):
            return False

        if self.instance.organization_contact and list(self.instance.organization_contact.required):
            return False

        return not list(self.instance.required)

    def is_valid(self):
        """The initiative is valid"""
        if self.instance.organization and list(self.instance.organization.errors):
            return False

        if self.instance.organization_contact and list(self.instance.organization_contact.errors):
            return False

        return not list(self.instance.errors)

    def is_staff(self, user):
        return user.is_staff

    initiate = Transition(
        EmptyState(),
        draft,
        name=_('Start'),
        description=_('The initiative will be created.'),
    )

    submit = Transition(
        [draft, needs_work],
        submitted,
        name=_('Submit'),
        description=_("The initiative will be submitted for review."),
        conditions=[is_complete, is_valid],
        automatic=False,
        effects=[
            RelatedTransitionEffect('activities', 'auto_submit'),
        ]
    )

    approve = Transition(
        submitted,
        approved,
        name=_('Approve'),
        description=_("The initiative will be visible in the frontend and "
                      "all completed activities will be open for contributions."),
        conditions=[is_complete, is_valid],
        automatic=False,
        permission=is_staff,
        effects=[
            RelatedTransitionEffect('activities', 'auto_approve'),
            NotificationEffect(InitiativeApprovedOwnerMessage)
        ]
    )

    request_changes = Transition(
        submitted,
        needs_work,
        name=_('Needs work'),
        description=_("The status of the initiative is set to 'Needs work'. "
                      "The initiator can edit and resubmit the initiative. "
                      "Don't forget to inform the initiator of the necessary adjustments."),
        conditions=[],
        automatic=False,
    )

    reject = Transition(
        [
            draft,
            submitted,
            needs_work,
        ],
        rejected,
        name=_('Reject'),
        description=_("Reject in case this initiative doesn't fit your program or the rules of the game. "
                      "The initiator will not be able to edit the initiative and "
                      "it won't show up on the search page in the front end. "
                      "The initiative will still be available in the back office and appear in your reporting. "),
        automatic=False,
        permission=is_staff,
        effects=[
            RelatedTransitionEffect('activities', 'reject'),
            NotificationEffect(InitiativeRejectedOwnerMessage)
        ]
    )

    cancel = Transition(
        approved,
        cancelled,
        name=_('Cancel'),
        description=_("Cancel if the initiative will not be executed. "
                      "The initiator will not be able to edit the initiative and "
                      "it won't show up on the search page in the front end. "
                      "The initiative will still be available in the back office and appear in your reporting."),
        automatic=False,
        effects=[
            RelatedTransitionEffect('activities', 'cancel'),
            NotificationEffect(InitiativeCancelledOwnerMessage)
        ]
    )

    delete = Transition(
        draft,
        deleted,
        name=_('Delete'),
        description=_("Delete the initiative if you don't want it to appear in your reporting. "
                      "The initiative will still be available in the back office."),
        automatic=False,
        hide_from_admin=True,
        effects=[
            RelatedTransitionEffect('activities', 'delete'),
        ]
    )

    restore = Transition(
        [
            rejected,
            cancelled,
            deleted
        ],
        needs_work,
        name=_('Restore'),
        description=_("The status of the initiative is set to 'needs work'. "
                      "The initiator can edit and submit the initiative again."),
        automatic=False,
        permission=is_staff,
        effects=[
            RelatedTransitionEffect('activities', 'restore'),
        ]
    )