borderless360 / django-logic Goto Github PK
View Code? Open in Web Editor NEWDjango Logic - easy way to implement state-based business logic with pure functions
License: MIT License
Django Logic - easy way to implement state-based business logic with pure functions
License: MIT License
Use pattern Command to implement permissions and conditions validation
An object should be locked only for a particular state field.
It needs to make hash function more generic and avoid collisions with the same app, model and state field names. Also, take into considerations Proxy Models.
django_logic.transition.Transition._get_hash
Accessing transition from a process like
model.process.transition()
Results in a call to Process.get_available_transitions
, which accepts a user
object, which it passes to permissions to check them. However, in this case user will be None
, because all we're doing is accessing a transition.
Essentially what this means is that all permission functions have to check whether they were passed an empty user and return True
in this case, otherwise transitions using them cannot be called.
In the Demo file we have the process.py
file but misses an import from model_utils import Choices
, where is this file ?
Process class names can be pretty long, so being able to set attribute names manually would be convenient
has_no_sales_invoices
duplicated.class InvoiceProcess(Process):
transitions = [
Transition(action_name='approve',
sources=[states.draft],
target=states.approved),
Transition(action_name='void',
sources=[states.draft, states.approved],
target=states.void,
conditions=[has_no_sales_invoices],
),
]
Process
class has an internal method named validate
which could be shadowed by __getattr__
if this process also has a transition named validate
, which is a pretty common name
Define clearly what is:
state
Transition
Process
ProcessManager
and why it should work as a mixinIt needs to provide more information on these questions:
Also:
At the moment failure handlers are not implemented at all
@staticmethod
def get_db_state(instance, field_name):
"""
Fetches state directly from db instead of model instance.
"""
return instance._meta.model.objects.values_list(field_name, flat=True).get(pk=instance.id)
This code assumes that the model is currently contained within the default queryset, which is not always the case. For instance, default queryset could be configured to exclude models with state=deleted
, in which case transitions would stop working for it.
Incorrectly implemented descriptors
AttributeError: 'SideEffectTasks' object has no attribute 'transition'
Implement StateField which shouldn't be possible to update via the save
method without explicitly defining so. Currently, it's implemented via mixin django_logic.process.ProcessManager
which can be deleted after.
However, it looks more like a workaround rather than a solution.
Try to avoid extra models.
Once it's done it should be possible to assign Process class explicitly to the model.
It should support the execution of side-effects and callbacks as:
During the execution, the object should be locked and the state should be changed to in_progress
It needs to use incr
method rather than get
and set
under State class
https://redis.io/topics/transactions
Hello, love the design of this package thinking of integrating it with our exisitng app.
I tried following your example of having 2 state fields in a model
process.py
class ApprovalProc(BaseProcess):
transitions = [
Transition(action_name='approve', sources=['pending', 'awaiting_seller'], target='approved'),
Transition(action_name='schedule', sources=['approved'], target='scheduled'),
Transition(action_name='reject', sources=['pending', 'awaiting_seller'], target='rejected'),
Transition(action_name='await_seller', sources=['pending'], target='awaiting_seller'),
Transition(action_name='cancel', sources=['pending', 'approved'], target='cancelled'),
# Action(action_name='update', side_effects=[]),
]
class AuctionProc(BaseProcess):
transitions = [
Transition(action_name='start', sources=['not_started', 'on_hold'], target='ongoing'),
Transition(action_name='pause', sources=['ongoing',], target='on_hold'),
Transition(action_name='end', sources=['ongoing',], target='ended'),
Transition(action_name='void', sources=['not_started', 'on_hold', 'ongoing', 'ended'], target='voided'),
# Action(action_name='update', side_effects=[update_data]),
]
ListingProcess = ProcessManager.bind_state_fields(listing_status=ApprovalProc,auction_status=AuctionProc )
class Listing(ListingProcess, models.Model):
listing_status = models.CharField(choices=ApprovalProc.states, default='pending', max_length=100, blank=True)
auction_status = models.CharField(choices=AuctionProc.states, default='not_started', max_length=100, blank=True)
When i try to play around with in the shell
l = Listing.objects.first()
l.process.approve()
I get this error
django_logic.exceptions.TransitionNotAllowed: Process class <class 'listings.process.AuctionProc'> has no transition with action name approve
transitions from the AuctionProc get called without any errors. So how do I call the other process?
It needs to implement a command which executes all provided functions as celery chain tasks.
During the execution, it should change the state to the in-progress state.
Once, the execution is finished, it should change to the target state.
If a failure occurs, it should change to the failure state and run a failure handler.
Provide acks_late=True
by default
Currently there is a bug with ProcessManager.bind_state_fields
method as it utilizes closures incorrectly:
for state_field, process_class in kwargs.items():
if not issubclass(process_class, Process):
raise TypeError('Must be a sub class of Process')
parameters[process_class.get_process_name()] = property(lambda self: process_class(
field_name=state_field,
instance=self))
parameters['state_fields'].append(state_field)
With this code, value of the process_class
variable inside the lambda function will always be the last class passed in kwargs to bind_state_fields
method.
getattr(tracking_event.tracking_event_process, 'gfdgdfg')
Traceback (most recent call last):
File "/home/vagrant/env/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-6-ebad20b256f2>", line 1, in <module>
getattr(tracking_event.tracking_event_process, 'gfdgdfg')
File "/home/vagrant/env/lib/python3.6/site-packages/django_logic/process.py", line 51, in __getattr__
raise TransitionNotAllowed('Transition not allowed')
django_logic.exceptions.TransitionNotAllowed: Transition not allowed
Seems like a bug, attribute user's attempting to get might not even be a transition
process
, and could be overwritten.s.process
raises an exception django_logic.exceptions.TransitionNotAllowed: Transition not allowed
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.