How to Assign a Task to a user in Django-Vieflow - django

class HelloWorldFlow(Flow):
process_class = HelloWorldProcess
######### Here I start the process where i enter some text
start = (
flow.Start(CreateProcessView, fields=["text"])
.Permission(auto_create=True)
.Next(this.approve)
)
######### Here i update the process view with the user name which i obtained from "asignedto"- which consist of all the user in a drop down
approve = (
flow.View(UpdateProcessView, fields=["asignedto"])
.Assign(lambda act: act.process.created_by)
.Permission(auto_create=True)
.Next(this.task_assign)
)
######### Below is the code where i am facing difficulty i am not able to know how to pass the user name in the .Assign()
######### This one works .Assign(username="Debasish"), however i want to assign the user based on previous workflow which is approve, where i selected from the drop down
######### This one do not work .Assign(username=this.approve.owner)
task_assign = (
flow.View(AssignTaskView)
.Assign(this.approve.owner)
.Next(this.check_approve)
)
######### Below this its working
check_approve = (
flow.If(lambda activation: activation.process.approved)
.Then(this.send)
.Else(this.end)
)
send = flow.Handler(this.send_hello_world_request).Next(this.end)
end = flow.End()
def send_hello_world_request(self, activation):
print(activation.process.text)

One of the advantages of the BPMN is the process traceability. You can't implement a process that would not persist the process decisions. That's very handy for the process performance analytics.
Each task in the BPMN is independent of each other and could perform decisions on a process data only. That gives flexibility to a flow diagram and allows to rearrange flow nodes since there is only data storage dependency between them.
So, the short answer - store a user in the Process model and use .Assign(lambda act: act.process.granted_user_for_a_task)

Related

Allowing users to select which flow to roll back to django-viewflow

Hey all i have been using viewflow as the workflow engine in my django project. I wanted to know if it was possible to allow users to 'select' which flow they wish to roll back to if lets say an approval was rejected.
Here , the director chooses 'Reject' , however it doesn't make sense to end the flow here , instead it should be a be a selectable 'roll back' , so that the the people down the line do not need to restart the entire process again.
Here's what i have done so far :
flows.py
#director will approve or dont approve
approve_by_director = flow.View(
UpdateProcessView,
form_class=DirectorApproveForm,
task_title="Approval By Director"
).Permission("cash.director"
).Next(this.check_director)
check_director = flow.If(
cond=lambda act: act.process.director,
task_title="Processing"
).Then(this.send).Else(this.justification)
#justifications for the roll back.
justification = flow.View(
JustificationView,
task_title="Justifications for Roll Back"
).Assign(lambda act: self.request.user
).Permission(auto_create=True
).Next(this.roll_back)
roll_back = flow.Handler(this.roll_back_call).Next(this.approve_by_preparer) ##<---- here , i am just sending it back to the 'preparer' , however it would be great if this could be dynamic!
end = flow.End()
def roll_back_call(self, activation):
esig = ESignatures.objects.filter(paymentVoucherProcess = activation.process).filter(voided = False)
docu = Attachment.objects.filter(paymentVoucherProcess = activation.process).filter(voided = False)
if len(esig) > 0 :
for sig in esig:
sig.voided = True
sig.save()
if len(docu) > 0 :
for doc in docu:
doc.voided = True
doc.save()
activation.process.preparer = False
activation.process.verifier = False
activation.process.treasury = False
activation.process.director = False
The problem here is that since the .next() node , i hard coded the phase in which i wish to roll back to, however this is not ideal as it would be optimal for the user to be able to 'select' which phase they were to send it back...
Therefore , I have two questions :
1. Is my method of the roll back correct? (maybe there is a better way to do the roll back instead of calling the process and hard code refreshing the fields)
2. Is there a way to select which part of the flow the user wish to roll back to?
Thanks and i would greatly appreciate anybody's advise
Actually, the good BPMN practice pattern is to finish flow as soon as possible, and do not involve complex cycles. That makes code simple, clean and easy to support. Simplifies reporting and keeps all revisions of data inside different process instances
With Viewflow flow-restart decision could be implemented as flow.View that records user decision and following flow.Handler that starts new flow instance using one of additional flow.StartFunction
Another option, is to use flow.View to record user decision and flow.Switch to step into another task.
Generally, BPMN separates user tasks and gateways, that lead to all decisions been recorded, before used.

Concurrency and django scaling horizontal

Hello I would like to ask you for advice for an application, I currently use Django cookie cutter for the project, I use as a library, Django MPTT in this model it must respect a certain hierarchy and that some objects do not find themselves in the same place as another to do this I need to isolate the objects to then perform my calculation and create my new objects. First of all since it could take a while I used Cellery to make my additions to the database I tried several concurrent accesses and I never had a rollback, I thought instead of using Cellery since I know how to scale the docker into several Django, I would integrate this creation into my view and use the #atomic decorator. The problem is that when we create objects at the same time on each different server (django1,django2,django3) I have a lot of rollback while with celeriac I have no problem. I don't understand why competition is more difficult in Django than in celery? Do you have any advice for me? For a better management of different Django servers at the same time, thank you in advance example :
Work in celery norollback :
#celery_app.task()
def create_user_in_matrix(user_id):
.....
new_human = HumanFaction.objects.select_for_update().filter(id=int(user_id))
with transaction.atomic():
new_human = new_human.first()
alderol = HumanFaction.objects.get(user_id=alderol_user_id)
print('selection utilisateur', new_human)
level = 1
level_max = HumanFaction.objects.aggregate(Max('level'))['level__max']
if level_max == 0:
level_max = 1
print('level maximum', level_max)
while level <= level_max and user_whole:
users = HumanFaction.objects.select_for_update().filter(level=level)
HumanFaction.objects.create(user=new_human, parent=aledol)
.....
Operates every third time in a django view during a colision test frequent rollback (django,1,2,3)
#transaction.atomic
def human_group(request):
......
new_human = HumanFaction.objects.select_for_update().filter(id=int(user_id))
with transaction.atomic():
new_human = new_human.first()
alderol = HumanFaction.objects.get(user_id=alderol_user_id)
print('selection utilisateur', new_human)
level = 1
level_max = HumanFaction.objects.aggregate(Max('level'))['level__max']
if level_max == 0:
level_max = 1
print('level maximum', level_max)
while level <= level_max and user_whole:
users = HumanFaction.objects.select_for_update().filter(level=level)
HumanFaction.objects.create(user=new_human, parent=aledol)
.....

Django Viewflow - How do apply the same transition for multiple tasks

With viewflow, My usecase is this:
A user is assigned multiple tasks. He wants to select some of the task and apply the same transition(Approve/Reject) to them. How can he do this?
Nothing specific. Just to activate and complete activation per-task. For safety reasons, you need to pre .select_for_update all included processes.
Process.objects.filter(...).select_for_update()
for task in _list_of_tasks_:
activation = task.activate()
activation.prepare()
# do something
# activation.process.approved = False
# activation.process.save()
activation.done()

How to reuse template in Flask-appbuilder with exposed custom handlers?

It is a very specific question regarding Flask-appbuilder. During my development, I found FAB's ModelView is suitable for admin role, but need more user logic handlers/views for complex designs.
There is a many to many relationship between devices and users, since each device could be shared between many users, and each user could own many device. So there is a secondary table called accesses, describes the access control between devices and users. In this table, I add "isHost" to just if the user owns the device. Therefore, we have two roles: host and (regular) user. However, these roles are not two roles defined as other applications, since one man can be either host or user in same time. In a very simple application, enforce the user to switch two roles are not very convinient. That makes things worse.
Anyway, I need design some custom handlers with traditional Flask/Jinja2 templates. For example:
class PageView(ModelView):
# FAB default URL: "/pageview/list"
datamodel = SQLAInterface(Page)
list_columns = ['name', 'date', 'get_url']
#expose("/p/<string:url>")
def p(self, url):
title = urllib.unquote(url)
r = db.session.query(Page).filter_by(name = title).first()
if r:
md = r.markdown
parser = mistune.Markdown()
body = parser(md)
return self.render_template('page.html', title = title, body = body)
else:
return self.render_template('404.html'), 404
Above markdown page URL is simple, since it is a seperate UI. But if I goes to DeviceView/AccountView/AccessView for list/show/add/edit operations. I realized that I need a unique styles of UI.
So, now how can I reuse the existing templates/widgets of FAB with custom sqlalchemy queries? Here is my code for DeviceView.
class DeviceView(ModelView):
datamodel = SQLAInterface(Device)
related_views = [EventView, AccessView]
show_template = 'appbuilder/general/model/show_cascade.html'
edit_template = 'appbuilder/general/model/edit_cascade.html'
#expose('/host')
#has_access
def host(self):
base_filters = [['name', FilterStartsWith, 'S'],]
#if there is not return, FAB will throw error
return "host view:{}".format(repr(base_filters))
#expose('/my')
#has_access
def my(self):
# A pure testing method
rec = db.session.query(Access).filter_by(id = 1).all()
if rec:
for r in rec:
print "rec, acc:{}, dev:{}, host:{}".format(r.account_id, r.device_id, r.is_host)
return self.render_template('list.html', title = "My Accesses", body = "{}".format(repr(r)))
else:
return repr(None)
Besides sqlalchemy code with render_template(), I guess base_filters can also help to define custom queries, however, I have no idea how to get query result and get them rendered.
Please give me some reference code or example if possible. Actually I have grep keywords of "db.session/render_template/expoaw"in FAB's github sources. But no luck.

FK validation within Django

Good afternoon,
I have my django server running with a REST api on top to serve my mobile devices. Now, at some point, the mobile device will communicate with Django.
Let's say the device is asking Django to add an object in the database, and within that object, I need to set a FK like this:
objectA = ObjectA.objects.create(title=title,
category_id = c_id, order = order, equipment_id = e_id,
info_maintenance = info_m, info_security = info_s,
info_general = info_g, alphabetical_notation = alphabetical_notation,
allow_comments = allow_comments,
added_by_id = user_id,
last_modified_by_id = user_id)
If the e_id and c_id is received from my mobile devices, should I check before calling this creation if they actually still exists in the DB? That is two extra queries... but if they can avoid any problems, I don't mind!
Thanks a lot!
It think that Django creates constraint on Foreign Key by default ( might depend on database though ). This means that if your foreign keys point to something that does not exist, then saving will fail ( resulting in Exception on Python side ).
You can reduce it to a single query (it should be a single query at least, warning I haven't tested the code):
if MyObject.objects.filter(id__in=[e_id, c_id]).distinct().count() == 2:
# create the object
ObjectA.objects.create(...)
else:
# objects corresponding e_id and c_id do not exist, do NOT create ObjectA
You should always validate any information that's coming from a user or that can be altered by a determined user. It wouldn't be difficult for someone to sniff the traffic and start constructing their own REST requests to your server. Always clean and validate external data that's being added to the system.