Skip to content
This repository was archived by the owner on Sep 30, 2023. It is now read-only.

add state action #13

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions share/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import typing
from datetime import date
from datetime import date, timedelta
from functools import singledispatchmethod

from django.contrib.auth import get_user_model
from django.db import OperationalError, models, transaction
Expand Down Expand Up @@ -122,6 +123,7 @@ class Donation(models.Model):
default=states.PendingApprovalState.state_id(),
choices=states.DonationStateEnum.choices,
)
estimated_delivery_days = models.PositiveSmallIntegerField()
excepted_delivery_date = models.DateField(null=True)
events = models.JSONField(default=list)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
Expand All @@ -132,13 +134,26 @@ class Donation(models.Model):
def required_item_name(self) -> str:
return self.required_item.name

@singledispatchmethod
def perform_action(self, arg):
raise NotImplementedError("Unknown action")

@perform_action.register
def _approval_action(self, action: states.ApprovalAction):
self.excepted_delivery_date = date.today() + timedelta(
days=self.estimated_delivery_days
)

def calc_state(self) -> states.State:
current_state = states.get_state(self.state)
for raw_event in self.events:
e = states.get_event(raw_event)
if e.timestamp < self.modified_at.timestamp():
continue
current_state = current_state.apply(e)
current_state, action = current_state.apply(e)
if action is not None:
self.perform_action(action)

return current_state

def set_event(self, user, raw_event: typing.Dict):
Expand Down
52 changes: 32 additions & 20 deletions share/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
from pydantic import BaseModel, Field, validator


class Action:
pass


class ApprovalAction(Action):
pass


class CancelAction(Action):
pass


class Event(BaseModel):
name: str = ""
timestamp: float = Field(default_factory=time.time)
Expand Down Expand Up @@ -38,7 +50,7 @@ class DonationCancelledEvent(Event):

class State(ABC):
@abstractmethod
def apply(self, event: Event) -> "State":
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
raise NotImplementedError()

@classmethod
Expand All @@ -47,52 +59,52 @@ def state_id(cls) -> str:


class InvalidState(State):
def apply(self, event: Event) -> State:
return InvalidState()
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
return InvalidState(), None


class CancelledState(State):
def apply(self, event: Event) -> State:
return CancelledState()
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
return CancelledState(), CancelAction()


class CollectingState(State):
def apply(self, event: Event) -> State:
return CancelledState()
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
return CancelledState(), None


class PendingApprovalState(State):
def apply(self, event: Event) -> State:
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
if event.name == DonationApprovedEvent.event_id():
return PendingDispatchState()
return PendingDispatchState(), ApprovalAction()
elif event.name == DonationCancelledEvent.event_id():
return CancelledState()
return CancelledState(), None
else:
return InvalidState()
return InvalidState(), None


class PendingDispatchState(State):
def apply(self, event: Event) -> State:
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
if event.name == DonationDispatchedEvent.event_id():
return DoneState()
return DoneState(), None
# return PendingDeliveryState()
elif event.name == DonationCancelledEvent.event_id():
return CancelledState()
return CancelledState(), None
else:
return InvalidState()
return InvalidState(), None


class PendingDeliveryState(State):
def apply(self, event: Event) -> State:
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
if event.name == DonationDeliveredEvent.event_id():
return DoneState()
return DoneState(), None
else:
return InvalidState()
return InvalidState(), None


class DoneState(State):
def apply(self, event: Event) -> State:
return InvalidState()
def apply(self, event: Event) -> typing.Tuple["State", typing.Optional[Action]]:
return InvalidState(), None


class DonationStateMachine:
Expand Down